/*
 * pgp-cfb.c
 *	  实现正常和 PGP 特定的 CFB 模式。
 *
 * Copyright (c) 2005 Marko Kreen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *	  notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *	  notice, this list of conditions and the following disclaimer in the
 *	  documentation和/或其他提供的材料与分发一起。
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, 或利润; 或业务中断)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * contrib/pgcrypto/pgp-cfb.c
 */

#include "postgres.h"

#include "pgp.h"
#include "px.h"

typedef int (*mix_data_t) (PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst);

struct PGP_CFB
{
	PX_Cipher  *ciph;
	int			block_size;
	int			pos;
	int			block_no;
	int			resync;
	uint8		fr[PGP_MAX_BLOCK];
	uint8		fre[PGP_MAX_BLOCK];
	uint8		encbuf[PGP_MAX_BLOCK];
};

int pgp_cfb_create(PGP_CFB **fc_ctx_p, int fc_algo, const uint8 *fc_key, int fc_key_len,
			   int fc_resync, uint8 *fc_iv)
{
	int			fc_res;
	PX_Cipher  *fc_ciph;
	PGP_CFB    *fc_ctx;

	fc_res = pgp_load_cipher(fc_algo, &fc_ciph);
	if (fc_res < 0)
		return fc_res;

	fc_res = px_cipher_init(fc_ciph, fc_key, fc_key_len, NULL);
	if (fc_res < 0)
	{
		px_cipher_free(fc_ciph);
		return fc_res;
	}

	fc_ctx = palloc0(sizeof(*fc_ctx));
	fc_ctx->ciph = fc_ciph;
	fc_ctx->block_size = px_cipher_block_size(fc_ciph);
	fc_ctx->resync = fc_resync;

	if (fc_iv)
		memcpy(fc_ctx->fr, fc_iv, fc_ctx->block_size);

	*fc_ctx_p = fc_ctx;
	return 0;
}

void pgp_cfb_free(PGP_CFB *fc_ctx)
{
	px_cipher_free(fc_ctx->ciph);
	px_memset(fc_ctx, 0, sizeof(*fc_ctx));
	pfree(fc_ctx);
}

/*
 * 正常CFB的数据处理。 (PGP_PKT_SYMENCRYPTED_DATA_MDC)
 */
static int fc_mix_encrypt_normal(PGP_CFB *fc_ctx, const uint8 *fc_data, int fc_len, uint8 *fc_dst)
{
	int			fc_i;

	for (fc_i = fc_ctx->pos; fc_i < fc_ctx->pos + fc_len; fc_i++)
		*fc_dst++ = fc_ctx->encbuf[fc_i] = fc_ctx->fre[fc_i] ^ (*fc_data++);
	fc_ctx->pos += fc_len;
	return fc_len;
}

static int fc_mix_decrypt_normal(PGP_CFB *fc_ctx, const uint8 *fc_data, int fc_len, uint8 *fc_dst)
{
	int			fc_i;

	for (fc_i = fc_ctx->pos; fc_i < fc_ctx->pos + fc_len; fc_i++)
	{
		fc_ctx->encbuf[fc_i] = *fc_data++;
		*fc_dst++ = fc_ctx->fre[fc_i] ^ fc_ctx->encbuf[fc_i];
	}
	fc_ctx->pos += fc_len;
	return fc_len;
}

/*
 * 旧PGP CFB模式的数据处理。 (PGP_PKT_SYMENCRYPTED_DATA)
 *
 * 目标是将恐怖隐藏在代码的其余部分，
 * 因此所有内容都集中在这里。
 */
static int fc_mix_encrypt_resync(PGP_CFB *fc_ctx, const uint8 *fc_data, int fc_len, uint8 *fc_dst)
{
	int			fc_i,
				fc_n;

	/* 块 #2 长度为 2 字节 */
	if (fc_ctx->block_no == 2)
	{
		fc_n = 2 - fc_ctx->pos;
		if (fc_len < fc_n)
			fc_n = fc_len;
		for (fc_i = fc_ctx->pos; fc_i < fc_ctx->pos + fc_n; fc_i++)
			*fc_dst++ = fc_ctx->encbuf[fc_i] = fc_ctx->fre[fc_i] ^ (*fc_data++);

		fc_ctx->pos += fc_n;
		fc_len -= fc_n;

		if (fc_ctx->pos == 2)
		{
			memcpy(fc_ctx->fr, fc_ctx->encbuf + 2, fc_ctx->block_size - 2);
			memcpy(fc_ctx->fr + fc_ctx->block_size - 2, fc_ctx->encbuf, 2);
			fc_ctx->pos = 0;
			return fc_n;
		}
	}
	for (fc_i = fc_ctx->pos; fc_i < fc_ctx->pos + fc_len; fc_i++)
		*fc_dst++ = fc_ctx->encbuf[fc_i] = fc_ctx->fre[fc_i] ^ (*fc_data++);
	fc_ctx->pos += fc_len;
	return fc_len;
}

static int fc_mix_decrypt_resync(PGP_CFB *fc_ctx, const uint8 *fc_data, int fc_len, uint8 *fc_dst)
{
	int			fc_i,
				fc_n;

	/* 块 #2 长度为 2 字节 */
	if (fc_ctx->block_no == 2)
	{
		fc_n = 2 - fc_ctx->pos;
		if (fc_len < fc_n)
			fc_n = fc_len;
		for (fc_i = fc_ctx->pos; fc_i < fc_ctx->pos + fc_n; fc_i++)
		{
			fc_ctx->encbuf[fc_i] = *fc_data++;
			*fc_dst++ = fc_ctx->fre[fc_i] ^ fc_ctx->encbuf[fc_i];
		}
		fc_ctx->pos += fc_n;
		fc_len -= fc_n;

		if (fc_ctx->pos == 2)
		{
			memcpy(fc_ctx->fr, fc_ctx->encbuf + 2, fc_ctx->block_size - 2);
			memcpy(fc_ctx->fr + fc_ctx->block_size - 2, fc_ctx->encbuf, 2);
			fc_ctx->pos = 0;
			return fc_n;
		}
	}
	for (fc_i = fc_ctx->pos; fc_i < fc_ctx->pos + fc_len; fc_i++)
	{
		fc_ctx->encbuf[fc_i] = *fc_data++;
		*fc_dst++ = fc_ctx->fre[fc_i] ^ fc_ctx->encbuf[fc_i];
	}
	fc_ctx->pos += fc_len;
	return fc_len;
}

/*
 * 加密和解密的通用代码。
 */
static int fc_cfb_process(PGP_CFB *fc_ctx, const uint8 *fc_data, int fc_len, uint8 *fc_dst,
			mix_data_t fc_mix_data)
{
	int			fc_n;
	int			fc_res;

	while (fc_len > 0 && fc_ctx->pos > 0)
	{
		fc_n = fc_ctx->block_size - fc_ctx->pos;
		if (fc_len < fc_n)
			fc_n = fc_len;

		fc_n = fc_mix_data(fc_ctx, fc_data, fc_n, fc_dst);
		fc_data += fc_n;
		fc_dst += fc_n;
		fc_len -= fc_n;

		if (fc_ctx->pos == fc_ctx->block_size)
		{
			memcpy(fc_ctx->fr, fc_ctx->encbuf, fc_ctx->block_size);
			fc_ctx->pos = 0;
		}
	}

	while (fc_len > 0)
	{
		unsigned	fc_rlen;

		px_cipher_encrypt(fc_ctx->ciph, 0, fc_ctx->fr, fc_ctx->block_size, fc_ctx->fre, &fc_rlen);
		if (fc_ctx->block_no < 5)
			fc_ctx->block_no++;

		fc_n = fc_ctx->block_size;
		if (fc_len < fc_n)
			fc_n = fc_len;

		fc_res = fc_mix_data(fc_ctx, fc_data, fc_n, fc_dst);
		fc_data += fc_res;
		fc_dst += fc_res;
		fc_len -= fc_res;

		if (fc_ctx->pos == fc_ctx->block_size)
		{
			memcpy(fc_ctx->fr, fc_ctx->encbuf, fc_ctx->block_size);
			fc_ctx->pos = 0;
		}
	}
	return 0;
}

/*
 * 公共接口
 */

int pgp_cfb_encrypt(PGP_CFB *fc_ctx, const uint8 *fc_data, int fc_len, uint8 *fc_dst)
{
	mix_data_t	fc_mix = fc_ctx->resync ? fc_mix_encrypt_resync : fc_mix_encrypt_normal;

	return fc_cfb_process(fc_ctx, fc_data, fc_len, fc_dst, fc_mix);
}

int pgp_cfb_decrypt(PGP_CFB *fc_ctx, const uint8 *fc_data, int fc_len, uint8 *fc_dst)
{
	mix_data_t	fc_mix = fc_ctx->resync ? fc_mix_decrypt_resync : fc_mix_decrypt_normal;

	return fc_cfb_process(fc_ctx, fc_data, fc_len, fc_dst, fc_mix);
}
